﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Markup;
using agree.configuration;
using System.Runtime.CompilerServices;

using alib;
using alib.Debugging;
using alib.Enumerable;

using agree.schema;

namespace agree
{
	using composition;

	public class TdlTypePred : xaml_functor_monad,
		ρρTdlType
	{
		public TdlTypePred(IRtParent parent)
			: base(parent)
		{
		}
		public TdlTypePred(IRtParent parent, TdlType tdl_type)
			: this(parent)
		{
			this.tdl_type = tdl_type;
		}

		///////////////////////////////////////////////////////////////////////
		///
		[AccessedThroughProperty("TdlType")]
		TdlType tdl_type;
		public TdlType TdlType
		{
			get { return this.tdl_type; }
			set { this.tdl_type = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////

		protected override IFunctor compile()
		{
			return new f_pred<Tdl>(this, tdl => tdl.TdlType == this.TdlType);
		}
	};


	public sealed class TdlTokenizerFactory : xaml_functor_monad,
		ρρAgreeConfig,
		ρρTdlLetterSets
	{
		public TdlTokenizerFactory(IRtParent parent)
			: base(parent)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		AgreeConfig config;
		public AgreeConfig AgreeConfig
		{
			get { return this.config; }
			set { this.config = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		/// 
		TdlLetterSets letter_sets;
		public TdlLetterSets TdlLetterSets
		{
			get { return letter_sets; }
			set { this.letter_sets = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		protected override IFunctor compile()
		{
			return new f_tdltok(this);
		}

		public sealed class f_tdltok : f_primitive<Tdl, TdlTokenizer>
		{
			public f_tdltok(TdlTokenizerFactory tf)
				: base(tf)
			{
				this.tf = tf;
			}
			TdlTokenizerFactory tf;
			protected override TdlTokenizer func(Tdl tdl)
			{
				var tdlt = new TdlTokenizer(tf, tdl);
				tdlt.AgreeConfig = tf.AgreeConfig;
				tdlt.TdlLetterSets = tf.letter_sets;
				return tdlt;
			}
		};
	};


	[ContentProperty("TypeTokenGroups")]
	[UsableDuringInitialization(true)]
	public partial class TypeDictionaryLoader : _monad<TypeDictionary, IActiveObj<IdentifierTokenGroup>>,
		ρρTypeDictionary
	{
		public TypeDictionaryLoader(IRtParent parent, TypeDictionary td, IActiveObj<IdentifierTokenGroup> type_defs)
			: base(parent, td, type_defs)
		{
		}
		public TypeDictionaryLoader(IRtParent parent)
			: base(parent)
		{
		}

		public TypeDictionary TypeDictionary
		{
			get { return this.t0; }
			set { this.t0 = value; }
		}

		public IActiveObj<IdentifierTokenGroup> TypeTokenGroups
		{
			get { return this.t1; }
			set { this.t1 = value; }
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// initialize Type dictionary with all Type names 
		/// Populate type hierarchy from the specified tokens
		/// Aggregate Type token streams into groups and load the authored type hierarchy from these identifier groups.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public override Object ProvideValue(IServiceProvider sp)
		{
			List<IdentifierTokenGroup> rgtdg = new List<IdentifierTokenGroup>();

			TaskCompletionSource<TypeDictionary> tcs = new TaskCompletionSource<TypeDictionary>();

			ConsDefs cd;
			TdlTok κnew;
			String s;
			Type Δnew;

			t1.Subscribe(itg =>
			{
				/// Since we make two passes on the enumerable, pre-load the grouping computation if necessary
				rgtdg.Add(itg);

				if (!itg.f_append)
				{
					s = (κnew = (cd = itg.constraints).identifier).Text;

					if (itg.κκParents.Count == 0 && cd.tdl_type != TdlType.NodeLabel)
						TdlTokenizer.ErrorExit(κnew, "Type defines no parents.");

					if (t0.TryGetValue(s, out Δnew))
					{
						//TdlTokenizer.ErrorExit(id_tok, String.Format("{0} already defined", id_tok.i_s));
						t0.Remove(s);
					}
					Δnew = t0.AddType(cd);
				}
			}, () =>
			{
				// check and build each type's parent types
				foreach (IdentifierTokenGroup itg in rgtdg)
				{
					s = (κnew = (cd = itg.constraints).identifier).Text;

					if (itg.f_append)
					{
						Type Δmaster;
						if (!t0.TryGetValue(s, out Δmaster))
							TdlTokenizer.ErrorExit(κnew, "Cannot append type information to undefined type.");
						Δmaster.SetConstraints(cd);
					}

					Type Δcur = t0[s];
					foreach (TdlTok κParent in itg.κκParents)
					{
						if (!t0.TryGetValue(κParent.Text, out Δnew))
							TdlTokenizer.ErrorExit(κParent, "Type uses undefined parent type {0}", κParent.Text);
						if (Δcur == Δnew)
							TdlTokenizer.ErrorExit(κParent, "Type {0} must not refer to itself as a parent", κParent.Text);
						Δcur.AddParent(Δnew);
					}
				}

				tcs.SetResult(t0);
			});

			return tcs.Task.Result;
		}
	};
}
